/********************************************************************************

* @File name: ftm.c

* @Author: Pan Zhimin

* @Version: 1.0

* @Date: 2023-8-21

* @Description: FTM SA & FA TEST

******************************************************************************* */

#include "ftm.h"


/****************************本地变量****************************************/
static uint32_t loop_tick = 0;

#if HAS_LAMP
static uint32_t lamp_tick = 0;
#endif

static int8_t	g_press_key_times = 0;
static uint8_t  g_fa_test_step = FA_STEP_NONE;

static uint16_t receive_length = 0;
static uint16_t receive_length_slave = 0;

static uint8_t 	receive_buffer[RECV_BUF_SIZE] = { 0 };
static uint8_t 	receive_buffer_slave[RECV_BUF_SIZE] = { 0 };

static devinfo_t g_devinfo = { 0 };

static const devinfo_t default_devinfo = {
	.bootmode = MODE_SA,

	.sn = DEFAULT_SN_DEVICE_ID_VAL,
	.sn_len = 0,

	.did = {0},
	.did_len = 0,

	.ble_uuid = BLE_UUID_DEFAULT,
	.ble_major = BLE_MAJOR_DEFAULT,
	.ble_minor = BLE_MINOR_DEFAULT,

	.flag = DEVINFO_NV_FLAG,

#if HAS_POWER_METER
	.vol_ratio_delta = 0,
	.cur_ratio_delta = 0,
	.pow_ratio_delta = 0,
#endif
};



/****************************GPIO TEST***************************************/
static void gpio_config(uint32_t pin, GPIOModeTypeDef mode) {
	uint32_t gpio = pin << 1 >> 1;

	if ((pin & GPIO_B) == GPIO_B) {
		GPIOB_ModeCfg(gpio, mode);
	} else {
		GPIOA_ModeCfg(gpio, mode);
	}
}

static void gpio_write(uint32_t pin, bool isOn) {
	uint32_t gpio = pin << 1 >> 1;

	if ((pin & GPIO_B) == GPIO_B) {
		if (isOn) {
			GPIOB_SetBits(gpio);
		} else {
			GPIOB_ResetBits(gpio);
		}
	} else {
		if (isOn) {
			GPIOA_SetBits(gpio);
		} else {
			GPIOA_ResetBits(gpio);
		}
	}
}

static bool gpio_read(uint32_t pin) {
	uint32_t gpio = pin << 1 >> 1;

	if ((pin & GPIO_B) == GPIO_B) {
		return GPIOB_ReadPortPin(gpio) > 0;
	} else {
		return GPIOA_ReadPortPin(gpio) > 0;
	}
}


/****************************Static functions********************************/
static int32_t usr_nv_read(uint32_t filepath, uint8_t* data, uint32_t size) {
	int32_t ret = -1;
	EEPROM_READ(filepath, data, size);
	return ret = 0;
}

static int32_t usr_nv_write(uint32_t filepath, uint8_t* data, uint32_t size) {
	int32_t ret = -1;
	EEPROM_ERASE(filepath, EEPROM_PAGE_SIZE);
	EEPROM_WRITE(filepath, data, size);
	return ret = 0;
}

static int32_t devinfo_store_to_nv(devinfo_t* devinfo) {
	devinfo_t info;
	if (usr_nv_read(DEVINFO_NV_PATH, (uint8_t*)&info, sizeof(info)) == 0) {
		if (memcmp(devinfo, &info, sizeof(info)) == 0) {
			return 0;
		}
	}
	return usr_nv_write(DEVINFO_NV_PATH, (uint8_t*)devinfo, sizeof(*devinfo));
}

static int32_t devinfo_load_from_nv(devinfo_t* devinfo) {
	devinfo_t info;
	int32_t ret = -1;

	if (usr_nv_read(DEVINFO_NV_PATH, (uint8_t*)&info, sizeof(info)) == 0) {
		if (info.flag == DEVINFO_NV_FLAG) {
			memcpy(devinfo, &info, sizeof(info));
			ret = 0;
		}
	}
	return ret;
}

static int32_t devinfo_store(void) {
	return devinfo_store_to_nv(&g_devinfo);
}

static int32_t devinfo_load(void) {
	if (devinfo_load_from_nv(&g_devinfo) != 0) {
		memcpy(&g_devinfo, &default_devinfo, sizeof(g_devinfo));
		// Note: 除了生产命令, 不能去修改文件
		// devinfo_store();
	}
	return 0;
}


/****************************static functions********************************/
static uint32_t now_ms(void) {
#if (CFG_DEVICE_TYPE == 1624 || CFG_DEVICE_TYPE == 1625 || CFG_DEVICE_TYPE == 1648 || CFG_DEVICE_TYPE == 1649 || CFG_DEVICE_TYPE == 1004 || CFG_DEVICE_TYPE == 803 || CFG_DEVICE_TYPE == 900 || CFG_DEVICE_TYPE == 966 || CFG_DEVICE_TYPE == 968 || CFG_DEVICE_TYPE == 980 || CFG_DEVICE_TYPE == 985 || (CFG_DEVICE_TYPE/100) == 24326 || CFG_DEVICE_TYPE == 1026 || CFG_DEVICE_TYPE == 760 || CFG_DEVICE_TYPE == 1036 || CFG_DEVICE_TYPE == 1039)
	return (uint32_t)((SysTick->CNT / 60000) & 0xffffffff);
#elif (CFG_DEVICE_TYPE == 988 || CFG_DEVICE_TYPE == 212)
	return (uint32_t)(zy_timer_tick & 0xffffffff);
#else
	return (uint32_t)(zq_timer_tick & 0xffffffff);
#endif
}

/*
* Description: 	50ms定时标志
* Parameter:
* Return:		定时已到 true, 反之 false
* Others:
*/
static bool is_loop_tick() {
	return loop_tick < now_ms();
}

/*
* Description: 	重置50ms定时标志
* Parameter:
* Return:
* Others:
*/
static void reset_loop_tick(void) {
	loop_tick = now_ms() + 50;
}

#if HAS_LAMP
/*
* Description: 	检查是否到了1.0s
* Parameter:
* Return:
* Others:
*/
static bool is_lamp_tick(void) {
	return lamp_tick < now_ms();
}

/*
* Description: 	重置1.0s定时标志
* Parameter:
* Return:
* Others:
*/
static void reset_lamp_tick() {
	lamp_tick = now_ms() + 1000;
}
#endif

/*
* Description: 	设置FA测试步骤
* Parameter:
* Return:
* Others:
*/
static void set_fa_test_step(uint8_t step) {
	g_fa_test_step = step;
}

/*
* Description: 	获取FA测试步骤
* Parameter:
* Return:		FA_STEP_XX
* Others:
*/
static uint8_t get_fa_test_step(void) {
	return g_fa_test_step;
}


/****************************Basic functions*********************************/
static uint16_t get_u16(uint8_t* data) {
	return (data[0] << 8) | data[1];
}

static bool pkt_valid(uint8_t* data) {
	uint8_t pkt_prefix[] = PKT_PREFIX;

	if (memcmp(data, pkt_prefix, sizeof(pkt_prefix)) != 0) {
		return false;
	}

	uint16_t cmd = get_u16(&data[8]) & 0xf000;

	return (cmd == FTM_MASTER_CMD_PREFIX) || (cmd == FTM_SLAVE_CMD_PREFIX);
}

static uint8_t ftm_pkt_sum(uint8_t* data, uint32_t size) {
	uint8_t s = 0;
	for (uint32_t i = 0; i < size; i++) {
		s += data[i];
	}
	return s;
}

static uint8_t business_pkt_sum(uint8_t* data, uint32_t size) {
	uint8_t s = 0;
	for (uint32_t i = 0; i < size; i++) {
		s ^= data[i];
	}
	return s;
}

static int8_t get_pc_pkt(uint8_t* data, uint32_t rx_len, pkt_t* pkt) {
	uint8_t pkt_prefix[] = PKT_PREFIX;

	if (rx_len && data[0] != pkt_prefix[0]) {
		return -1;
	}

	if (rx_len < PKT_HEAD_SZ) {
		return 0;
	}

	if (!pkt_valid(data)) {
		return -1;
	}

	//     #define     PKT_HEAD_SZ  (sizeof(PKT_PREFIX) + 2 /* cmd(0xaabb) */ + 1 /* data_len(0xcc) */)
	uint32_t pkt_len = PKT_HEAD_SZ /* 8 + 2 + 1 */ + data[10] + 2 /* checksum(0xdd) + suffix(0xBB) */;


	// AA 00 FF FF FF FF FF FF 81 01 [0C] [48 44 36 31 39 30 35 39 43 30 30 31] D0 BB
	// PKT_HEAD_SZ = 11,	data_len = data[10] = 0x0C, 		pkt_len = 11 + 0x0c + 2 = 25

	if (rx_len < pkt_len || data[pkt_len - 1] != PKT_SUFFIX) {
		/* 1, 收到的长度 比 应该的长度要少 */
		/* 2, 收到的尾巴 不是 0xBB */
		// 都需要继续接收uart数据
		// FTM_LOG("1.1, rx_len:%d < data[10]:%d", rx_len, pkt_len);
		// FTM_LOG("1.2, data[pkt_len - 1]: 0x%02x != 0xBB ?", data[pkt_len - 1]);
		// FTM_LOG("1.3, receive data continue");
		return 0;
	}

	if (ftm_pkt_sum(data, pkt_len - 2) != data[pkt_len - 2]) {
		/* 3, 这里数据都收全了, 但checkSum不对, 则需要clear, 否则解析pkt永远为NULL, 不能再执行其他命令了 */
		// FTM_LOG("2.0, check sum:0x%02x != data[len-1]:0x%02x, clear rx_buffer", ftm_pkt_sum(data, pkt_len - 2), data[pkt_len - 2]);
		return -1;
	}

	pkt->cmd = get_u16(&data[8]);
	pkt->data_len = data[10];
	pkt->data = &data[PKT_HEAD_SZ];
	pkt->len = pkt_len;

	return 1;
}

/*
* Description: 	解析数据包, 转成pkt格式
* Parameter:
* @data:		数据内容
* @rx_len:		数据长度
* Return：		&pkt, -1:clear, 1:OK
* Others:		Cat1和M0业务数据包
* Note:			pkt_send(澳柯玛项目): 低字节在前, 高字节在后
				{AA, 07, 00, 01, D1, 7D, 00}
*/
static int8_t get_business_pkt(uint8_t* data, uint32_t rx_len, pkt_t* pkt) {
#if (ZQ_COMM_PLATFORM == 8910)
	uint16_t pkt_len = 0;
	if (rx_len >= 4 && (pkt_len = (data[1] | (data[2] << 8))) >= 4) {
		if (rx_len >= pkt_len && data[0] == 0xAA) {
			if (business_pkt_sum(data, pkt_len) == 0) {
				pkt->cmd = data[3];
				pkt->data = &data[5];			// EXAM: 007d = 125 (版本号)
				pkt->data_len = pkt_len - 5;	// 5 = sizeof(IotUartPkg)
				pkt->len = pkt_len;
				return 1;
			}
		}
}
#elif (ZQ_COMM_PLATFORM == 8850) || (ZQ_COMM_PLATFORM == 485) || (ZQ_COMM_PLATFORM == 582)
	ZqCommPkg* pkg = (ZqCommPkg*)data;
	if (rx_len >= pkg->len && pkg->flag == 0xAA) {
		if (business_pkt_sum(data, pkg->len) == 0) {
			pkt->cmd = pkg->cmd;
			pkt->data_len = pkg->len - sizeof(ZqCommPkg);
			pkt->data = pkg->data;
			pkt->len = pkg->len;
			return 1;
		}
	}
#else
#error "please add ZQ_COMM_PLATFORM == xx"
#endif	

	return -1;
}

static bool is_ftm_mode(void) {
	if (g_devinfo.bootmode == 0) {
		devinfo_load();
	}
	return g_devinfo.bootmode < MODE_WK;
}

static bool is_ftm_fa_mode(void) {
	if (g_devinfo.bootmode == 0) {
		devinfo_load();
	}
	return g_devinfo.bootmode == MODE_FA;
}

#if HAS_LAMP
static void display_str(char* str) {
	DISP_STR(str);
	DISP_REFRESH();
}


/*
* Description: 	显示数字
* Parameter:
* @num:			数据(整数、小数、负数)
* Others:
* Note:			例如 arr = {321, 43, 5, 32.1, 4.3, 0.5, 0};
				结果 959 = {321, 43, 5, 32.1, 4.3, 0.5, 0}
					 961 = {21, 43, 5, 32, 4, 0, 0}
				负数同

*/
#if (CFG_DEVICE_TYPE == 885 || CFG_DEVICE_TYPE == 891)
static void display_num(float num) {
	if (num - (int)num != 0) {
		// 小数
#if (ZQ_DISPLAY_PANEL_TYPE == 1 || ZQ_DISPLAY_PANEL_TYPE == 4 || ZQ_DISPLAY_PANEL_TYPE == 5)
		DISP_FLOAT(num * 10);	// 比如32.1, 需要显示成321 并 加个小数点
#else
		display_num((int)num);
#endif	
	} else {
		// 整数
		if (num > 99 || num < -99) {
#if (ZQ_DISPLAY_PANEL_TYPE == 1 || ZQ_DISPLAY_PANEL_TYPE == 4 || ZQ_DISPLAY_PANEL_TYPE == 5)
			// 3位的整数 [只能显示2位, 这里我特殊处理一下(显示3位float, 手动把点号灭掉)]
			DISP_FLOAT(num);
			DISP_ICON(disp_item_dot, false);
#else
			DISP_INT((int)num % 100);
#endif
		} else {
#if (ZQ_DISPLAY_PANEL_TYPE == 5)
			DISP_INT(num);
#else
			DISP_INT(num);
#endif
		}
	}

	DISP_REFRESH();
}
#else
static void display_num(int num) {
	DISP_FLOAT(num);
	DISP_REFRESH();
}
#endif


/*
* Description: 	各个step按顺序显示字符串
* Parameter:
* @increase:	自增1
* Return：
* Others:		FA各步骤自动加1, 其中按键可重复的不变化(按step是否变化);
				SA的按键自动加1
*/
static void fa_show_step_str() {
	static uint8_t g_fa_str_index = 0;
	static uint8_t g_fa_str_step = 0;

	uint8_t step = get_fa_test_step();
	char str[8] = { 0 };

	if (is_ftm_fa_mode()) {
		if (g_fa_str_step != step) {
			g_fa_str_step = step;
			g_fa_str_index++;
		}
	} else {
		g_fa_str_index++;
	}

#if (CFG_DEVICE_TYPE == 212)
	sprintf(str, "%X%X", g_fa_str_index, g_fa_str_index);
#else
	sprintf(str, "%X%X%X", g_fa_str_index, g_fa_str_index, g_fa_str_index);
#endif

	FTM_LOG("Current Step(%s): %d, Show String: \"%s\"\r\n", (is_ftm_fa_mode() ? "FA" : "SA | WK"), step, str);

	display_str(str);
}
#endif

static int keyScan(int* key) {
	int status = -1;

	int key_status_new[ZQ_KEY_NUM];
	static int g_key_status[ZQ_KEY_NUM];

	uint8_t raw = READ_KEYS();

	if (raw != 0) {
		FTM_LOG("RAW = [0x%02x]", raw);
	}

	for (int i = 0; i < ZQ_KEY_NUM; i++) {
		key_status_new[i] = (raw & (1 << i)) ? 1 : 0;

		if (g_key_status[i] && !key_status_new[i]) {
			// Down -> UP
			g_key_status[i] = 0;
			status = 0;
			*key = i;
			FTM_LOG("key%d: Down -> UP", i);
			break;
		} else if (!g_key_status[i] && key_status_new[i]) {
			// Up --> Down
			g_key_status[i] = 1;
			status = 1;
			*key = i;
			FTM_LOG("key%d: Up --> Down", i);
			break;
		}
	}

	return status;
}

static void print_m0_cmd_result(uint16_t cmd, uint8_t* data, uint32_t data_len) {
	char cmdStr[20] = { 0 };

	if (cmd == FTM_SN) {
		strcpy(cmdStr, "SN");
	} else if (cmd == FTM_HW) {
		strcpy(cmdStr, "HW");
	} else if (cmd == FTM_SW) {
		strcpy(cmdStr, "SW");
	} else if (cmd == FTM_BT_UUID) {
		strcpy(cmdStr, "UUID");
	} else if (cmd == FTM_BOOTMODE) {
		strcpy(cmdStr, "BOOTMODE");
	} else if (cmd == FTM_DEVICE_ID) {
		strcpy(cmdStr, "DID");
	} else if (cmd == FTM_RELAY_1 || cmd == FTM_RELAY_2 || cmd == FTM_RELAY_3) {
		strcpy(cmdStr, "RELAY X");
	} else if (cmd == FTM_ZHAOMING || cmd == FTM_LENGNING || cmd == FTM_ZHENGFA) {
		strcpy(cmdStr, "LED IO");
	} else if (cmd == FTM_POWER_METER) {
		strcpy(cmdStr, "POWER METER");
	} else if (cmd == FTM_IRDA) {
		strcpy(cmdStr, "IRDA");
	} else if (cmd == FTM_BEEP) {
		strcpy(cmdStr, "BEEP");
	} else if (cmd == FTM_NTC_1 || cmd == FTM_NTC_2) {
		strcpy(cmdStr, "NTC X");
	} else if (cmd == FTM_485_TEST) {
		strcpy(cmdStr, "485");
	} else if (cmd == FTM_COM_TEST) {
		strcpy(cmdStr, "COM");
	} else if (cmd == FTM_M0_REPORT_FA_RESULT) {
		strcpy(cmdStr, "FA UPLOAD");
	}

	if (data_len == 1) {
		if (cmd == FTM_BOOTMODE) {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, (data[0] == MODE_SA ? "SA | OK" : (data[0] == MODE_FA ? "FA | OK" : "WORK")));
		} else {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, (data[0] == OK ? "Ok" : (data[0] == FAIL ? "Fail" : (data[0] == NOT_SUPPORT ? "Not Support" : "Not Set"))));
		}
	} else {
		if (cmd == FTM_BT_UUID) {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]-0x%02X(%d)-0x%02X(%d))", cmd, cmdStr, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], ((data[16] << 8) + data[17]), ((data[16] << 8) + data[17]), ((data[18] << 8) + data[19]), ((data[18] << 8) + data[19]));
		} else {
			char szData[150] = { 0 };
			memcpy(szData, (char*)data, data_len);
			szData[data_len] = 0;
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, szData);
		}
	}
}

/*
* Description: 	发送包给xx端
* Parameter:
* @uart_num:	目标(1: PC, 0: Cat1)
* @cmd:			命令ID
* @data:		数据内容
* @size:		数据长度
* Return：
* Others:
*/
static void ftm_pkt_send(uint8_t port, uint16_t cmd, uint8_t* data, uint8_t size) {
	uint8_t tx_buffer[64 * 3];
	uint8_t pkt_prefix[] = PKT_PREFIX;
	uint16_t off = sizeof(pkt_prefix);

	memcpy(tx_buffer, pkt_prefix, off);

	tx_buffer[off++] = ((cmd >> 8) & 0xff);
	tx_buffer[off++] = cmd & 0xff;
	tx_buffer[off++] = size;
	if (size) {
		memcpy(&tx_buffer[off], data, size);
		off += size;
	}
	tx_buffer[off++] = ftm_pkt_sum(tx_buffer, PKT_HEAD_SZ + size);
	tx_buffer[off++] = PKT_SUFFIX;

	FTM_HEX_BUF_LOG("tx_buffer", tx_buffer, off);

	if (port == 1) {
		// M0发给PC的数据, 统一在此打印查看
		print_m0_cmd_result(cmd, data, size);

		SEND_TO_MASTER(tx_buffer, off);
	} else {
		SEND_TO_SLAVE(tx_buffer, off);
	}
}

/*
* Description: 	发送包给Cat1端
* Parameter:
* @cmd:			命令ID
* @data:		数据内容 (业务格式)
* @size:		数据长度
* Return：
* Others:		IotResponse(澳柯玛项目): 低字节在前, 高字节在后
*/
static void businiss_pkt_send(uint16_t cmd, uint8_t* data, uint8_t size) {
	uint8_t tx_buffer[64 * 4];
	uint16_t off = 0;

#if (ZQ_COMM_PLATFORM == 8910)
	tx_buffer[off++] = 0xAA;
	tx_buffer[off++] = (size + 5) & 0xff;
	tx_buffer[off++] = (size + 5) >> 8;
	tx_buffer[off++] = cmd;
	tx_buffer[off++] = 0;	// 先把checkSum位置填0
	if (size) {
		memcpy(&tx_buffer[off], data, size);
		off += size;
	}
	tx_buffer[4] = business_pkt_sum(tx_buffer, off);
#elif (ZQ_COMM_PLATFORM == 8850) || (ZQ_COMM_PLATFORM == 485) || (ZQ_COMM_PLATFORM == 582)
	// 0x17: AA 05 B8 16 01, AA 05 B8 15 02
	if (size) {
		memcpy(tx_buffer + sizeof(ZqCommPkg), data, size);
	}
	ZqCommPkg* pkg = (ZqCommPkg*)tx_buffer;
	pkg->flag = 0xAA;
	pkg->len = sizeof(ZqCommPkg) + size;
	pkg->cmd = cmd;
	pkg->checksum = 0;
	pkg->checksum = business_pkt_sum(tx_buffer, pkg->len);
	off = sizeof(ZqCommPkg) + size;
#else
#error "please add ZQ_COMM_PLATFORM == xx"
#endif

	FTM_HEX_BUF_LOG("tx_buffer", tx_buffer, off);

	SEND_TO_SLAVE(tx_buffer, off);
}


/****************************SA functions************************************/
/*
* Description: 	解析M0的基本信息
* Parameter:
* @data:		数据
* @data_len:	数据长度
* @buf: 		返回要填入的值
* Return:		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_cmd01(uint8_t* buf) {
#if (ZQ_COMM_PLATFORM == 8910)
	typedef struct {
		uint8_t com_protocol_ver;
		char sw_ver[ZY_VER_MAX_LEN];
		char ble_uuid[16];
		char ble_mac[6];
		uint8_t bootmode;
		uint32_t cat1_reset_stage;
		uint32_t cat1_reset_time;
	} saCmd01RspPkg;
#elif (ZQ_COMM_PLATFORM == 8850) || (ZQ_COMM_PLATFORM == 485) || (ZQ_COMM_PLATFORM == 582)
	typedef DtuC01ResponsePkg saCmd01RspPkg;
#else
#error "please add ZQ_COMM_PLATFORM == xx"	
#endif

	saCmd01RspPkg pkg = {
		.com_protocol_ver = COMM_PROTOCOL_VER,
		.bootmode = g_devinfo.bootmode
	};

	uint8_t data_len = sizeof(pkg);
	memcpy(buf, &pkg, data_len);

	FTM_LOG("READ: bootmode = %ld, len(0x%02x - %d)", g_devinfo.bootmode, data_len, data_len);

	return data_len;
}

/*
* Description: 	读SN号
* Parameter:
* @buf: 		返回要填入的值(如果是未分配或默认值, 则为0xff)
* Return:		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_sn_read(uint8_t* buf) {
	uint8_t data_len = 0;

	if (g_devinfo.sn_len <= 0 || memcmp((char*)g_devinfo.sn, DEFAULT_SN_DEVICE_ID_VAL, strlen(DEFAULT_SN_DEVICE_ID_VAL)) == 0) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = g_devinfo.sn_len;
		strcpy((char*)buf, (char*)g_devinfo.sn);
	}

	FTM_LOG("READ: sn(%s), len(0x%02x - %d)", (data_len == 1 ? "Not Set" : (char*)buf), data_len, data_len);

	return data_len;
}

/*
* Description: 	写SN号
* Parameter:
* @buf: 		SN内容
* @sn_len:		SN长度
* Return：
* Others:
*/
static bool ftm_sa_sn_write(uint8_t* buf, int32_t sn_len) {
	bool result = false;

	if (buf && sn_len > 0) {
		g_devinfo.sn_len = sn_len;
		memcpy(g_devinfo.sn, (char*)buf, sn_len);
		g_devinfo.sn[sn_len] = 0;

		FTM_LOG("WRITE: sn(%s), len(0x%02lx - %ld)", (char*)g_devinfo.sn, sn_len, sn_len);

		devinfo_store();

		result = true;
	}

	return	result;
}

/*
* Description: 	读硬件版本号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_hw_read(uint8_t* buf) {
	uint8_t data_len = 0;

#ifdef AXD_HARD_VERSION
	strcpy((char*)buf, AXD_HARD_VERSION);
#elif (defined ZY_M0_HW_VER && defined ZQ_VER_PREFIX)
	if (ZY_M0_HW_VER >= 10000) {
		sprintf((char*)buf, "%sH_V%1d.%1d.%1d.%02d", ZQ_VER_PREFIX, ZY_M0_HW_VER / 10000, (ZY_M0_HW_VER % 10000) / 1000, (ZY_M0_HW_VER % 1000) / 100, ZY_M0_HW_VER % 100);
	} else {
		sprintf((char*)buf, "%sH_V%1d.%1d.%1d", ZQ_VER_PREFIX, ZY_M0_HW_VER / 100, (ZY_M0_HW_VER % 100) / 10, (ZY_M0_HW_VER % 100) % 10);
	}
#elif (defined ZQ_M0_HW_VER && defined ZQ_VER_PREFIX)
	sprintf((char*)buf, "%sH_V%s", ZQ_VER_PREFIX, ZQ_M0_HW_VER);
#else
	buf[data_len++] = RSP_NOT_SET;
#endif

	data_len = strlen((char*)buf);

	FTM_LOG("READ: hw(%s), len(0x%02x - %d)", buf, data_len, data_len);

	return data_len;
}

/*
* Description: 	读软件版本号
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_sw_read(uint8_t* buf) {
	uint8_t data_len = 0;

	uint8_t sw[100] = { 0 };
	if (!buf) {
		buf = sw;
	}

#if (defined VER_PREFIX && defined VER_MAJOR && defined VER_MINOR && defined VER_REVISION)
	sprintf((char*)buf, "%s_V%d.%d.%d", VER_PREFIX, VER_MAJOR, VER_MINOR, VER_REVISION);
#elif (defined ZY_M0_SW_VER && defined ZQ_VER_PREFIX)
	if (ZY_M0_SW_VER >= 10000) {
		sprintf((char*)buf, "%sM_V%1d.%1d.%1d.%02d", ZQ_VER_PREFIX, ZY_M0_SW_VER / 10000, (ZY_M0_SW_VER % 10000) / 1000, (ZY_M0_SW_VER % 1000) / 100, ZY_M0_SW_VER % 100);
	} else {
		sprintf((char*)buf, "%sM_V%1d.%1d.%1d", ZQ_VER_PREFIX, ZY_M0_SW_VER / 100, (ZY_M0_SW_VER % 100) / 10, (ZY_M0_SW_VER % 100) % 10);
	}
#elif (defined ZQ_M0_SW_VER && defined ZQ_VER_PREFIX)
	sprintf((char*)buf, "%sM_V%s", ZQ_VER_PREFIX, ZQ_M0_SW_VER);
#else
	buf[data_len++] = RSP_NOT_SET;
#endif

	data_len = strlen((char*)buf);

	if (strlen((char*)sw)) {
		FTM_LOG("FTM START (%s)============, pid = %d, mode = %ld(%s)", sw, CFG_DEVICE_TYPE, g_devinfo.bootmode, g_devinfo.bootmode <= MODE_SA ? "SA" : (g_devinfo.bootmode <= MODE_FA ? "FA" : "WORK"));
	} else {
		FTM_LOG("READ: sw(%s), len(0x%02x - %d)", buf, data_len, data_len);
	}

	return data_len;
}

/*
* Description: 	读M0启动模式
* Parameter:
* @buf: 		返回要填入的值
* Return：		要发送数据的长度
* Others:
*/
static uint8_t ftm_bootmode_read(uint8_t* buf) {
	uint8_t data_len = 0;

	buf[data_len++] = g_devinfo.bootmode;

	FTM_LOG("READ: bootMode(%ld)", g_devinfo.bootmode);

	return data_len;
}

/*
* Description: 	写M0启动模式
* Parameter:
* @bootmode: 	启动模式
* Return：
* Others:
* Note:	AKM959: （PC - UART1-2230400）cmd = 0x81， data[0] = 0x01 或 0x02
		AA 06 00 81 2C 01
		AA 06 00 81 2F 02
*/
bool ftm_bootmode_write(uint8_t bootmode, bool reboot) {
	if (bootmode < MODE_SA || bootmode > MODE_WK) {
		return false;
	}

	g_devinfo.bootmode = bootmode;
	devinfo_store();

	FTM_LOG("WRITE: bootMode(%ld, %s)", g_devinfo.bootmode, (reboot ? "Reboot" : "Don't reboot"));

	if (reboot) {
		delay1ms(2 * 1000);	// 取代DELAYs(2), 期间不能再重复处理cat1过来的命令了！
		SYS_ResetExecute();
	}

	return true;
}

#if HAS_DEVICE_ID
/*
* Description: 	读device id
* Parameter:
* @buf: 		返回要填入的值(如果是未分配或默认值, 则为0xff)
* Return:		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_did_read(uint8_t* buf) {
	uint8_t data_len = 0;

	if (g_devinfo.did_len <= 0) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		data_len = g_devinfo.did_len;
		strcpy((char*)buf, (char*)g_devinfo.did);
	}

	FTM_LOG("READ: did(%s), len(0x%02x - %d)", (data_len == 1 ? "Not Set" : (char*)buf), data_len, data_len);

	return data_len;
}

/*
* Description: 	写device id
* Parameter:
* @buf: 		内容
* @sn_len:		长度
* Return：
* Others:
*/
static bool ftm_sa_did_write(uint8_t* buf, int32_t did_len) {
	bool result = false;

	if (buf && did_len > 0) {
		g_devinfo.did_len = did_len;
		memcpy(g_devinfo.did, (char*)buf, did_len);
		g_devinfo.did[did_len] = 0;

		FTM_LOG("WRITE: did(%s), len(0x%02lx - %ld)", (char*)g_devinfo.did, did_len, did_len);

		devinfo_store();

		result = true;
	}

	return	result;
}
#endif

#if HAS_UUID
/*
* Description: 	读ble(uuid/major/minor)
* Parameter:
* @buf: 		返回要填入的值(如果是未分配或默认值, 则为0xff)
* Return:		要发送数据的长度
* Others:
*/
static uint8_t ftm_sa_ble_read(uint8_t* buf) {
	uint8_t data_len = 0;

	uint8_t uuid_default[] = BLE_UUID_DEFAULT;
	int uuid_len = sizeof(uuid_default);

	if (/*memcmp(g_devinfo.ble_uuid, uuid_default, uuid_len) == 0 || */
		g_devinfo.ble_major == BLE_MAJOR_DEFAULT
		|| g_devinfo.ble_minor == BLE_MINOR_DEFAULT) {
		buf[data_len++] = RSP_NOT_SET;
	} else {
		memcpy((char*)buf + data_len, (char*)g_devinfo.ble_uuid, uuid_len);
		data_len += uuid_len;

		buf[data_len++] = (g_devinfo.ble_major >> 8) & 0xff;
		buf[data_len++] = g_devinfo.ble_major & 0xff;

		buf[data_len++] = (g_devinfo.ble_minor >> 8) & 0xff;
		buf[data_len++] = g_devinfo.ble_minor & 0xff;
	}

	FTM_HEX_BUF_LOG("BLE uuid", g_devinfo.ble_uuid, sizeof(g_devinfo.ble_uuid));
	FTM_LOG("READ: ble(0x%x, 0x%x), len(0x%02x - %d)", g_devinfo.ble_major, g_devinfo.ble_minor, data_len, data_len);

	return data_len;
}

/*
* Description: 	写ble(uuid/major/minor)
* Parameter:
* @buf: 		内容
* @sn_len:		长度
* Return：
* Others:
*/
static bool ftm_sa_ble_write(uint8_t* buf, int32_t ble_len) {
	bool result = false;

	uint8_t uuid_default[] = BLE_UUID_DEFAULT;
	int uuid_len = sizeof(uuid_default);

	if (buf && ble_len >= (uuid_len + sizeof(g_devinfo.ble_major) + sizeof(g_devinfo.ble_minor))) {
		memcpy(g_devinfo.ble_uuid, buf, uuid_len);
		g_devinfo.ble_major = (buf[uuid_len] << 8) + (buf[uuid_len + 1] & 0xff);
		g_devinfo.ble_minor = (buf[uuid_len + 2] << 8) + (buf[uuid_len + 3] & 0xff);

		FTM_HEX_BUF_LOG("BLE uuid", g_devinfo.ble_uuid, sizeof(g_devinfo.ble_uuid));

		FTM_LOG("WRITE: ble(0x%x, 0x%x), len(0x%02lx - %ld)", g_devinfo.ble_major, g_devinfo.ble_minor, ble_len, ble_len);

		devinfo_store();

		result = true;
	}

	return result;
}
#endif

#if HAS_POWER_METER
static bool ftm_sa_power_meter_cal(bool isOn, uint8_t* data) {
	bool result = true;

	uint32_t relay_pin = RELAY_1_GPIO;

	// 1, 配置继电器
	gpio_config(relay_pin, GPIO_ModeOut_PP_5mA);

	if (isOn) {
		// 2, 打开继电器
		gpio_write(relay_pin, true);
		FTM_LOG("Relay on, power meter test START");
	} else {
		// 3, 在继电器开启8s后, 差不多可以查询电压&电流&功率
		short vol, cur, pow;
		PowMeterGather(&vol, &cur, &pow);
		FTM_LOG("8850: vol=%d, cur=%d, pow=%d", vol, cur, pow);

		// 4, 计算从电能表读到的数据, 也转换成电压&电流&功率 (AA 00 FF FF FF FF FF FF 81 0B 08 B5 54 74 89 33 CB 55 34 C5 BB)
		uint8_t high, middle, low;

		// 电压: B5 54 [-33] => 82 21 => 21 82 => 218.2V => 218V
		high = data[1] - 0x33;
		low = data[0] - 0x33;
		int16_t dds3102_vol = (high >> 4) * 100 + (high & 0x0f) * 10 + (low >> 4) * 1 + ((low & 0x0f) > 4 ? 1 : 0);

		// 电流: 74 89 33 [-33] => 41 56 00 => 00 56 41 ==> 005.641A = 5641mA
		high = data[4] - 0x33;
		middle = data[3] - 0x33;
		low = data[2] - 0x33;
		int32_t dds3102_cur = (high >> 4) * 100000 + (high & 0x0f) * 10000 + (middle >> 4) * 1000 + (middle & 0x0f) * 100 + (low >> 4) * 10 + (low & 0x0f);


		// 功率: CB 55 34 [-33] => 98 22 01 => 01 22 98 => 01.2298kw = 1230w
		high = data[7] - 0x33;
		middle = data[6] - 0x33;
		low = data[5] - 0x33;
		int32_t dds3102_pow = (high >> 4) * 10000 + (high & 0x0f) * 1000 + (middle >> 4) * 100 + (middle & 0x0f) * 10 + (low >> 4) * 1 + (((low & 0x0f) > 4) ? 1 : 0);

		FTM_LOG("3102: vol=%d, cur=%ld, pow=%ld", dds3102_vol, dds3102_cur, dds3102_pow);

		g_devinfo.vol_ratio_delta = vol ? (dds3102_vol - vol) * 100 / vol : 0;
		g_devinfo.cur_ratio_delta = cur ? (dds3102_cur - cur) * 100 / cur : 0;
		g_devinfo.pow_ratio_delta = pow ? (dds3102_pow - pow) * 100 / pow : 0;

		FTM_LOG("Vol(r: %d), Cur(r: %d), Pow(r: %d)", g_devinfo.vol_ratio_delta, g_devinfo.cur_ratio_delta, g_devinfo.pow_ratio_delta);

		devinfo_store();

		// 5, 最后关闭打开继电器
		gpio_write(relay_pin, false);
		FTM_LOG("Relay off, power meter test END");
	}

	return result;
}
#endif

#if HAS_IRDA
static bool ftm_sa_irda_test(void) {
	bool result = false;

	uint16_t times = 0;
	bool old_state = false, new_state = false;

	for (int i = 0; i < 200; i++, WDT_CLEAN(), delay1ms(50)) {
		if ((new_state = !!gpio_read(IRDA_GPIO)) != old_state) {
			FTM_LOG("state change: %d ==> %d", old_state, new_state);
			old_state = new_state;
			if (++times >= 2) {
				result = true;
				break;
			}
		}
	}

	return result;
}
#endif

#if HAS_IO_LED
/*
* Description: 	LED IO 测试
* Parameter:
* @led_num:	led号(0/1/2)
* @is_fa_mode:	是否为FA模式
* Return：		true/false
* Others:
*/
static bool ftm_sa_led_test(int led_num, bool is_fa_mode) {
	bool result = false;

	uint32_t led_pins[] = {
		LED_1_GPIO,
		LED_2_GPIO,
		LED_3_GPIO,
	};

	uint32_t connect_to_pins[] = {
		CONNECT_TO_GPIO_1,
		CONNECT_TO_GPIO_2,
		CONNECT_TO_GPIO_3,
		CONNECT_TO_GPIO_4,
		CONNECT_TO_GPIO_5
	};

	int  valid_index = -1;

	if (!led_pins[led_num]) {
		FTM_LOG_ERR("/* ----------LED(%d) NOT DEFINED------------ */", led_num);
		return result;
	}

	// 1, 配置LED
	gpio_config(led_pins[led_num], GPIO_ModeOut_PP_5mA);

	if (!is_fa_mode) {
		/* FA模式的话就不必检查输出结果, 也就不需要配置这些GPIO口了 */
		for (int i = 0; (i < sizeof(connect_to_pins) / sizeof(connect_to_pins[0])); i++) {
			if (connect_to_pins[i]) {
				gpio_config(connect_to_pins[i], GPIO_ModeIN_PU);
			}
		}
	}

	// 3, 打开LED
	FTM_LOG("/* ----------LED(%d, 0x%x) ON------------ */", led_num, led_pins[led_num]);
	gpio_write(led_pins[led_num], true);

	/* SA模式延时0.5s, FA模式延时1s */
	DELAYms((is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY));

	/* FA模式的话就不必检查输出结果了 */
	if (!is_fa_mode) {
		for (int i = 0; i < sizeof(connect_to_pins) / sizeof(connect_to_pins[0]); i++) {
			// LED打开时, 读到低有效 (联通, 等于是接到GND)
			if (connect_to_pins[i] && (result = !gpio_read(connect_to_pins[i]))) {
				valid_index = i;
				FTM_LOG("[%s] ON(%dms): result = %s, IOVALUE: %d, valid_index: %d", (is_fa_mode ? BOOT_MODE_FA : BOOT_MODE_SA), (is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY), result ? "TRUE" : "FALSE", gpio_read(connect_to_pins[valid_index]), valid_index);
				break;
			}
		}
	}

	// 4, 关闭LED (不管开启后检测结果是否成功)
	FTM_LOG("/* ----------LED(%d, 0x%x) OFF------------ */", led_num, led_pins[led_num]);
	gpio_write(led_pins[led_num], false);

	/* SA模式延时0.5s, FA模式延时1s */
	DELAYms((is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY));

	/* FA模式的话就不必检查输出结果了 */
	if (!is_fa_mode && valid_index >= 0) {
		// LED关闭时, 读到高有效 (断开, 等于是悬空, 默认上拉)
		result = gpio_read(connect_to_pins[valid_index]);
		FTM_LOG("[%s] OFF(%dms): result = %s, IOVALUE: %d, valid_index: %d", (is_fa_mode ? BOOT_MODE_FA : BOOT_MODE_SA), (is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY), result ? "TRUE" : "FALSE", gpio_read(connect_to_pins[valid_index]), valid_index);
	}

	return result;
}
#endif

/*
* Description: 	继电器测试
* Parameter:
* @relay_num:	继电器号(0/1/2)
* @is_fa_mode:	是否为FA模式
* Return：		true/false
* Others:
*/
static bool ftm_sa_relay_test(uint8_t relay_num, bool is_fa_mode) {
	bool result = false;

	uint32_t relay_pins[] = {
		RELAY_1_GPIO,
		RELAY_2_GPIO,
		RELAY_3_GPIO,
#if (RELAY_NUM > 3)
		RELAY_4_GPIO
#endif
	};

	uint32_t connect_to_pins[] = {
		CONNECT_TO_GPIO_1,
		CONNECT_TO_GPIO_2,
		CONNECT_TO_GPIO_3,
		CONNECT_TO_GPIO_4,
		CONNECT_TO_GPIO_5
	};

	int  valid_index = -1;

	if (relay_num >= (sizeof(relay_pins) / sizeof(relay_pins[0])) || !relay_pins[relay_num]) {
		FTM_LOG_ERR("/* ----------RELAY(%d) NOT DEFINED------------ */", relay_num);
		return result;
	}

	// 1, 配置继电器
	gpio_config(relay_pins[relay_num], GPIO_ModeOut_PP_5mA);

	// 2, 配置可能连接的N个GPIO口 (上拉输入)
	if (!is_fa_mode) {
		/* FA模式的话就不必检查输出结果, 也就不需要配置这些GPIO口了 */
		for (int i = 0; i < sizeof(connect_to_pins) / sizeof(connect_to_pins[0]); i++) {
			if (connect_to_pins[i]) {
				gpio_config(connect_to_pins[i], GPIO_ModeIN_PU);
			}
		}
	}

	// 3, 打开继电器
	FTM_LOG("/* ----------RELAY(%d) ON------------ */", relay_num);
	gpio_write(relay_pins[relay_num], true);

	/* SA模式延时0.5s, FA模式延时1s */
	DELAYms((is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY));

	/* FA模式的话就不必检查输出结果了 */
	if (!is_fa_mode) {
		for (int i = 0; i < sizeof(connect_to_pins) / sizeof(connect_to_pins[0]); i++) {
			// 继电器打开时, 读到低有效 (联通, 等于是接到GND)
			if (connect_to_pins[i] && (result = !gpio_read(connect_to_pins[i]))) {
				valid_index = i;
				FTM_LOG("[%s] ON(%dms): result = %s, IOVALUE: %d, valid_index: %d", (is_fa_mode ? BOOT_MODE_FA : BOOT_MODE_SA), (is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY), result ? "TRUE" : "FALSE", gpio_read(connect_to_pins[valid_index]), valid_index);
				break;
			}
		}
	}

	// 4, 关闭继电器 (不管开启后检测结果是否成功)
	FTM_LOG("/* ----------RELAY(%d) OFF------------ */", relay_num);
	gpio_write(relay_pins[relay_num], false);

	/* SA模式延时0.5s, FA模式延时1s */
	DELAYms((is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY));

	/* FA模式的话就不必检查输出结果了 */
	if (!is_fa_mode && valid_index >= 0) {
		// 继电器关闭时, 读到高有效 (断开, 等于是悬空, 默认上拉)
		result = gpio_read(connect_to_pins[valid_index]);
		FTM_LOG("[%s] OFF(%dms): result = %s, IOVALUE: %d, valid_index: %d", (is_fa_mode ? BOOT_MODE_FA : BOOT_MODE_SA), (is_fa_mode ? FA_RELAY_TEST_DELAY : SA_RELAY_ON_OFF_DELAY), result ? "TRUE" : "FALSE", gpio_read(connect_to_pins[valid_index]), valid_index);
	}

	return result;
}

#if HAS_BEEP
static void buzzer_switch(bool isOn) {
	if (isOn) {
		gpio_config(BUZZER_GPIO, GPIO_ModeOut_PP_5mA);
		PWMX_CLKCfg(60);
		PWMX_CycleCfg(PWMX_Cycle_256);
		PWMX_ACTOUT(CH_PWM5, 256 / 2, Low_Level, ENABLE);
	} else {
		PWMX_ACTOUT(CH_PWM5, 256 / 2, Low_Level, DISABLE);
		gpio_write(BUZZER_GPIO, false);
	}
}

/*
* Description: 	蜂鸣器叫一下
* Parameter:
* Return：		true/false
* Others:
*/
static bool ftm_sa_beep_test(void) {
	// 蜂鸣器叫一下
	buzzer_switch(true);

	// 用这个函数代替DELAYms(200), 否则其中还会处理Cat1的消息
	for (int i = 0; i < 20; i++, WDT_CLEAN(), delay1ms(10));

	buzzer_switch(false);

	return true;
}
#endif

#if HAS_NTC_1
/*
* Description: 	温度传感器测试
* Parameter:
* Return：		电压范围[1.6, 1.7]返回成功
* Others:		外接10K的电阻(3.3分压)
*/
static bool ftm_sa_ntc1_test(void) {
	// 争取在500ms内获取结果,不能太慢
	short adc_calib = ADC_DataCalib_Rough();
	for (int i = 0; i < 50; i++, WDT_CLEAN(), delay1ms(10)) {
		AdcSamplingNtc1(adc_calib);
	}

	int32_t temp = AdcGetNtc1();

	FTM_LOG("temp() = %ld", temp);

	return (temp >= (3300 / 2 - 100)) && (temp <= (3300 / 2 + 100));
}
#endif

#if HAS_NTC_2
/*
* Description: 	温度传感器测试
* Parameter:
* Return：		电压范围[1.6, 1.7]返回成功
* Others:		外接10K的电阻(3.3分压)
*/
static bool ftm_sa_ntc2_test(void) {
	// 争取在500ms内获取结果,不能太慢
	short adc_calib = ADC_DataCalib_Rough();
	for (int i = 0; i < 50; i++, WDT_CLEAN(), delay1ms(10)) {
		AdcSamplingNtc2(adc_calib);
	}

	int32_t temp = AdcGetNtc2();

	FTM_LOG("temp() = %ld", temp);

	return (temp >= (3300 / 2 - 100)) && (temp <= (3300 / 2 + 100));
}
#endif

/*
* Description: 	按键处理, 每50ms扫描
* Parameter:
* Return：
* Others:		SA模式下按键5次可以把Cat1切到Work状态 .
*/
static void ftm_sa_key_process(void) {
	int key_code = -1;
	int status = keyScan(&key_code);

	if (key_code == 0) {
		if (status == 1) {
			// SA 中循环显示灯的, 有按键就暂停lamp的显示
			set_fa_test_step(FA_STEP_NONE);

			// 然后显示一个空字符串
#if HAS_LAMP
			display_str(KEY_DOWN_STR);
#endif
#if HAS_BEEP
			ftm_sa_beep_test();
#endif
		} else if (status == 0) {
#if HAS_LAMP
			fa_show_step_str();
#endif

#if (defined SA_CHANGE_TO_WORK_MODE) || (defined SA_CHANGE_TO_FA_MODE)
			g_press_key_times++;
#endif

#ifdef SA_CHANGE_TO_WORK_MODE
			if (g_press_key_times == 5) {
#ifdef CHANGE_TO_XX_MODE_MO_ONLY
				FTM_LOG("1, SA --> WORK (Don't reboot, change M0 mode Only), press key1 (%d)", g_press_key_times);
				ftm_bootmode_write(MODE_WK, false);
				g_press_key_times = -15;
#else
				FTM_LOG("1, SA --> WORK (Don't reboot, change with Cat1 mode), press key1 (%d)", g_press_key_times);
				uint8_t buf[] = { MODE_WK };
				ftm_pkt_send(0, FTM_SET_SLAVE_TO_WK, buf, sizeof(buf));
#endif
			}
#endif
#ifdef SA_CHANGE_TO_FA_MODE
			if (g_press_key_times == -10) {
				/* Note: 这里发命令让Cat1设置成SA模式; 在Cat1给的回应里处理, 如果是g_press_key_times==10的情况, 则也需要把M0设置到SA状态 */
#ifdef CHANGE_TO_XX_MODE_MO_ONLY
				FTM_LOG("1, SA --> FA (Reboot, change M0 mode Only), press key1 (%d)", g_press_key_times);
				ftm_bootmode_write(MODE_FA, true);
#else
				FTM_LOG("1, SA --> FA (Reboot, change with Cat1 mode), press key1 (%d)", g_press_key_times);
				uint8_t buf[] = { MODE_FA };
				ftm_pkt_send(0, FTM_SET_SLAVE_TO_WK, buf, sizeof(buf));
#endif
			}
#endif	
		}
	}
}


/****************************FA functions************************************/
/*
* Description: 	n个继电器自动开关测试
* Parameter:
* Return：
* Others:		原本顺序执行n个继电器的开和关, 现在改为挨个测试.
				因为Cat1发cmd01, 会因为多个继电器loop时间太长, 而得不到数据导致M0重启
*/
static void ftm_fa_relay_test(void) {
	uint8_t step = get_fa_test_step();

	// FTM_LOG("=== RELAY ===, step = %d", step);
	for (int i = 0; i < RELAY_NUM; i++) {
		uint8_t relay_step = FA_STEP_RELAY_START + i, relay_again_step = FA_STEP_RELAY_START + RELAY_NUM + i;

		if (step == relay_step - 1 || step == relay_again_step - 1) {
			FTM_LOG("RELAY - %d %s", i, (step == relay_step - 1 ? "" : "AGAIN"));
			ftm_sa_relay_test(i, true);
			set_fa_test_step(step + 1);
		}
	}
}

#if HAS_MP3
static void ftm_fa_mp3_test() {
	uint8_t step = get_fa_test_step();

	for (int i = 0; i < MP3_NUM; i++) {
		if (step == FA_STEP_MP3_START + i * 2 - 1) {
			static uint32_t delayTimes = 0;
			if (delayTimes++ < 40) {
				// 2s后Cat1应该能运行起来并接收M0的命令了吧?!
				return;
			}

			// 这里不循环, 等待当前step测试OK		
			set_fa_test_step(step + 1);

			FTM_LOG("MP3 Test Start ...");
			ftm_pkt_send(0, FTM_PLAY_MP3, NULL, 0);
		}
	}
}
#endif

#if HAS_LAMP
/*
* Description: 	操作所有的icon灯
* Parameter:
* Return：
* Others:
*/
static void all_leds_enable(bool enable) {
	for (int i = 0; i < LED_ICON_NUM; i++) {
		DISP_ICON(i, enable);
	}
	DISP_REFRESH();
}

/*
* Description: 	数码灯管循环测试, 每1.5s
* Parameter:
* Return：
* Others:		SA/FA同一套闪烁方式; 有按键打断就不显示
*/
static void ftm_lamp_test(void) {
	static int16_t arrayIndex = 0;
#ifdef FA_LED_BANK_TEST
	int array[] = { 23, -34, 42 };
#else
	int array[] = { 234, -342, 425 };
#endif

	uint8_t step = get_fa_test_step();

	if (step == FA_STEP_LAMP - 1 || step == FA_STEP_LAMP) {
		// 一直在这里循环, 直到下一个step测试OK
		set_fa_test_step(FA_STEP_LAMP);

#if (CFG_DEVICE_TYPE == 212)
		// 212项目, 负号不是一个icon, 而是数码管中间的一横, 当int超过3位时, 就会出现一横
		if (arrayIndex % 2) {
			array[arrayIndex % (sizeof(array) / sizeof(array[0]))] %= 100;
		}
#endif

		display_num(array[arrayIndex % (sizeof(array) / sizeof(array[0]))]);
		all_leds_enable(++arrayIndex % 2);
	} else {
		if (arrayIndex % 2 != 0) {
			FTM_LOG("! If close all leds repeat, NTC show temp will no dot or minus !");
			all_leds_enable(false);
			arrayIndex = 0;
		}
	}
}
#endif

/*
* Description: 	n个按键测试, 每50ms扫描
* Parameter:
* Return：
* Others:
* NOTE:			FA_STEP_KEY_LAMP这个就自动进入下一项测试, 不可以重复按键(以节约时间), 前面两个按键可以重复按
*/
static void ftm_fa_key_test(void) {
	uint8_t step = get_fa_test_step();

	int key_code = -1;
	int status = keyScan(&key_code);

	if (status == 1) {
		for (int i = 0; i < ZQ_KEY_NUM; i++) {
			uint8_t key_down_step = FA_STEP_KEY_START + i * 2;
			if ((key_code == i) && ((key_down_step - 1 <= step) && (step <= key_down_step + 1))) {
				set_fa_test_step(key_down_step);
#if HAS_LAMP
				display_str(KEY_DOWN_STR);
#endif
#if HAS_BEEP
				ftm_sa_beep_test();
#endif
				// 1, FA测试中如果没有继电器, {FA_STEP_NONE=0, FA_STEP_LAMP=1, FA_STEP_KEY_1_DOWN=2, ...}
				// 2, 对于arr的KEY_NUM大于其内容, 末尾默认是{key:0, step:0}, 而current step = FA_STEP_LAMP:1
				// 3, 就有可能判断到key_code==key:0 && (step:0 -1 <= FA_STEP_LAMP <= step:0 +1), 则会set step to step:0(FA_STEP_NONE)而重复FA流程
				break;
			}
		}
	} else if (status == 0) {
		// 有键释放
		for (int i = 0; i < ZQ_KEY_NUM; i++) {
			uint8_t key_down_step = FA_STEP_KEY_START + i * 2;
			if ((key_code == i) && (step == key_down_step)) {
				set_fa_test_step(key_down_step + 1);

#if HAS_LAMP
				fa_show_step_str();
#endif

				// 这里对KEY_1特殊处理 (FA中就放在key1, 放到其他key不方便, 容易进入下一个step !)
				if (key_code == 0) {
#ifdef FA_LED_BANK_TEST
#if HAS_LAMP
					display_str(LED_FA_KEY_STR);
#endif
					DELAYms(3 * 1000);
					set_fa_test_step(FA_STEP_COMPLETE);
					return;
#else
#if (defined FA_CHANGE_TO_WORK_MODE) || (defined FA_CHANGE_TO_SA_MODE)
					g_press_key_times++;
#endif

#ifdef FA_CHANGE_TO_WORK_MODE
					if (g_press_key_times == 5) {
						/* Note: 这里发命令让Cat1设置成Work模式; 在Cat1给的回应里处理, 如果是g_press_key_times==5的情况, 则也需要把M0设置到Work状态 */
#ifdef CHANGE_TO_XX_MODE_MO_ONLY
						FTM_LOG("2, FA --> WORK (Don't reboot, change M0 mode Only), press key1 (%d)", g_press_key_times);
						ftm_bootmode_write(MODE_WK, false);
#else
						FTM_LOG("2, FA --> WORK (Don't reboot, change with Cat1 mode), press key1 (%d)", g_press_key_times);
						uint8_t buf[] = { MODE_WK };
						ftm_pkt_send(0, FTM_SET_SLAVE_TO_WK, buf, sizeof(buf));
#endif
					}
#endif
#ifdef FA_CHANGE_TO_SA_MODE
					if (g_press_key_times == 10) {
						/* Note: 这里发命令让Cat1设置成SA模式; 在Cat1给的回应里处理, 如果是g_press_key_times==10的情况, 则也需要把M0设置到SA状态 */
#ifdef CHANGE_TO_XX_MODE_MO_ONLY
						FTM_LOG("2, FA --> SA (Reboot, change M0 mode Only), press key1 (%d)", g_press_key_times);
						ftm_bootmode_write(MODE_SA, true);
#else
						FTM_LOG("2, FA --> SA (Reboot, change with Cat1 mode), press key1 (%d)", g_press_key_times);
						uint8_t buf[] = { MODE_SA };
						ftm_pkt_send(0, FTM_SET_SLAVE_TO_WK, buf, sizeof(buf));
#endif
					}
#endif	
#endif
				}

				// 原因同上
				break;
			}
		}
	}
}

#if HAS_IO_LED
/*
* Description: 	n个LED自动开关测试
* Parameter:
* Return：
* Others:		原本顺序执行n个LED的开和关, 现在改为挨个测试.
				因为Cat1发cmd01, 会因为多个LED的loop时间太长, 而得不到数据导致M0重启
*/
static void ftm_fa_led_test(void) {
	uint8_t step = get_fa_test_step();

	// FTM_LOG("=== LED ===, step = %d", step);

	for (int i = 0; i < 3; i++) {
		uint8_t led_step = FA_STEP_LED_START + i, led_again_step = FA_STEP_LED_START + 3 + i;

		if (step == led_step - 1 || step == led_again_step - 1) {
			FTM_LOG("LED - %d %s", i, (step == led_step - 1 ? "" : "AGAIN"));
			ftm_sa_led_test(i, true);
			set_fa_test_step(step + 1);
		}
	}
}
#endif

#if HAS_DOOR_1
/*
* Description: 	门开关测试, 每50ms扫描
* Parameter:
* Return：
* Others:		按下显示
*/
static void ftm_fa_door1_test(void) {
	uint8_t step = get_fa_test_step();

	// FTM_LOG("=== DOOR ===, step = %d", step);

	if (step == FA_STEP_DOOR_1_START - 1) {
		// door 1 START
		gpio_config(DOOR_1_GPIO, GPIO_ModeIN_PU);
		DELAYms(50);
		if (!gpio_read(DOOR_1_GPIO)) {
			FTM_LOG("DOOR ON");

			set_fa_test_step(FA_STEP_DOOR_1_START);
#if HAS_LAMP
			fa_show_step_str();
#endif
		}
	} else if (step == FA_STEP_DOOR_1_END - 1) {
		// door 1 END
		gpio_config(DOOR_1_GPIO, GPIO_ModeIN_PU);
		DELAYms(50);
		if (gpio_read(DOOR_1_GPIO)) {
			FTM_LOG("DOOR OFF");

			set_fa_test_step(FA_STEP_DOOR_1_END);
		}
	}
}
#endif

#if HAS_DOOR_2
/*
* Description: 	门开关测试, 每50ms扫描
* Parameter:
* Return：
* Others:		按下显示
*/
static void ftm_fa_door2_test(void) {
	uint8_t step = get_fa_test_step();

	// FTM_LOG("=== DOOR R ===, step = %d", step);

	if (step == FA_STEP_DOOR_2_START - 1) {
		// door 2 START
		gpio_config(DOOR_2_GPIO, GPIO_ModeIN_PU);
		DELAYms(50);
		if (!gpio_read(DOOR_2_GPIO)) {
			FTM_LOG("DOOR_R ON");

			set_fa_test_step(FA_STEP_DOOR_2_START);
#if HAS_LAMP
			fa_show_step_str();
#endif
		}
	} else if (step == FA_STEP_DOOR_2_END - 1) {
		// door 2 END
		gpio_config(DOOR_2_GPIO, GPIO_ModeIN_PU);
		DELAYms(50);
		if (gpio_read(DOOR_2_GPIO)) {
			FTM_LOG("DOOR_R OFF");

			set_fa_test_step(FA_STEP_DOOR_2_END);
		}
	}
}
#endif

#if HAS_SYS
/*
* Description: 	系统开关测试, 每50ms扫描
* Parameter:
* Return：
* Others:		按下显示
*/
static void ftm_fa_sys_test(void) {
	uint8_t step = get_fa_test_step();

	//FTM_LOG("=== SYS IO ===, step = %d", step);

	if (step == FA_STEP_SYS_START - 1) {
		// sys ON
		gpio_config(SYS_GPIO, GPIO_ModeIN_PU);
		DELAYms(50);
		if (!gpio_read(SYS_GPIO)) {
			FTM_LOG("SYS ON");

			set_fa_test_step(FA_STEP_SYS_START);
#if HAS_LAMP
			fa_show_step_str();
#endif
		}
	} else if (step == FA_STEP_SYS_END - 1) {
		// sys OFF
		gpio_config(SYS_GPIO, GPIO_ModeIN_PU);
		DELAYms(50);
		if (gpio_read(SYS_GPIO)) {
			FTM_LOG("SYS OFF");

			set_fa_test_step(FA_STEP_SYS_END);
		}
	}
}
#endif

#if HAS_NTC_1
/*
* Description: 	温度传感器测试, 每50ms扫描
* Parameter:
* Return：
* Others:		没有外接电阻显示E2, 否则显示动态温度, 直到0℃
*/
static void ftm_fa_ntc1_test(void) {
	uint8_t step = get_fa_test_step();

	// 如果上一步是LAMP, 则需要延时几秒, 否则太快显示温度就看不到LAMP了
#if HAS_LAMP
	if (FA_STEP_NTC_1_START - 1 == FA_STEP_LAMP) {
		static uint32_t delayTimes = 0;
		if (delayTimes++ < 110) {
			// 5.5s 大约可以让数码管循环显示两轮
			return;
		}
	}
#endif

	if (step == FA_STEP_NTC_1_START - 1 || step == FA_STEP_NTC_1_START) {
		// 一直在这里循环, 直到下一个step测试OK
		set_fa_test_step(FA_STEP_NTC_1_START);

		// FTM_LOG("=== NTC1 ===, step = %d", step);
		short adc_calib = ADC_DataCalib_Rough();
		AdcSamplingRef(adc_calib);

		// 防止上一个step显示字符串太快而看不到, 取代DELAYs(2), 因为测量温度需要采样很多次 .
		for (int i = 0; i < 200; i++, WDT_CLEAN(), delay1ms(10)) {
			AdcSamplingNtc1(adc_calib);
		}

		short temp = GetMeasureTem();
		FTM_LOG("tem_measure_value() = %d", temp);

		if (IS_NTC_FAULT(temp)) {
#if HAS_LAMP
			display_str(SENSOR_E2_STR);
#endif
			FTM_LOG("IS_NTC_FAULT(), show \"%s\"", SENSOR_E2_STR);
		} else {
#if !((ZQ_DISPLAY_PANEL_TYPE == 1) || (ZQ_DISPLAY_PANEL_TYPE == 4) || (ZQ_DISPLAY_PANEL_TYPE == 5) || (ZQ_DISPLAY_PANEL_TYPE == 6))
			// 暂时可认为分体的就是两位数码管, 四舍五入
			temp = ROUND10(temp);
#endif
			// 查看电阻值xx对应0℃ (tem_tables | tem_om_table)
			if (temp >= -10 && temp <= 10) {
				set_fa_test_step(FA_STEP_NTC_1_END);
#if HAS_LAMP
				fa_show_step_str();
#endif
			} else {
#if HAS_LAMP
#if (CFG_DEVICE_TYPE == 885 || CFG_DEVICE_TYPE == 891)
				display_num(temp / 10.0f);
#else
#if (CFG_DEVICE_TYPE == 212)
				// 212项目显示前两位
				DISP_INT(temp / 10);
				DISP_REFRESH();
#else
				display_num(temp);
#endif
#endif
#endif
			}
		}
	}
}
#endif

#if HAS_NTC_2
/*
* Description: 	温度传感器测试, 每50ms扫描
* Parameter:
* Return：
* Others:		没有外接电阻显示E2, 否则显示动态温度, 直到0℃
*/
static void ftm_fa_ntc2_test(void) {
	uint8_t step = get_fa_test_step();

	if (step == FA_STEP_NTC_2_START - 1 || step == FA_STEP_NTC_2_START) {
		// 一直在这里循环, 直到下一个step测试OK
		set_fa_test_step(FA_STEP_NTC_2_START);

		// FTM_LOG("=== NTC2 ===, step = %d", step);
		short adc_calib = ADC_DataCalib_Rough();
		AdcSamplingRef(adc_calib);

		// 防止上一个step显示字符串太快而看不到, 取代DELAYs(2), 因为测量温度需要采样很多次 .
		for (int i = 0; i < 200; i++, WDT_CLEAN(), delay1ms(10)) {
			AdcSamplingNtc2(adc_calib);
		}

		extern short GetDefrostMeasureTem(void);
		short temp = GetDefrostMeasureTem();
		FTM_LOG("tem_measure_value() = %d", temp);

		if (IS_NTC_FAULT(temp)) {
#if HAS_LAMP
			display_str(SENSOR_E2_STR);
#endif
			FTM_LOG("IS_NTC_FAULT(), show \"%s\"", SENSOR_E2_STR);
		} else {
			// tem_tables查看电阻值xx对应0℃
			if (temp >= -10 && temp <= 10) {
				set_fa_test_step(FA_STEP_NTC_2_END);
#if HAS_LAMP
				fa_show_step_str();
#endif
			} else {
#if HAS_LAMP
#if (CFG_DEVICE_TYPE == 885 || CFG_DEVICE_TYPE == 891)
				display_num(temp / 10.0f);
#else
				display_num(temp);
#endif
#endif
			}
		}
	}
}
#endif

#if HAS_NTC_3
/*
* Description: 	温度传感器测试 (LED电流测试)
* Parameter:
* Return：		电流范围[10.6, 11.0]返回成功
* Others:		LED 灯检测接入时注意电流方向, 应该从 1pin 流入, 2pin 流出, 电流最大不超过 5A
*/
static void ftm_fa_ntc3_test(void) {
	uint8_t step = get_fa_test_step();

	// FTM_LOG("=== NTC3 ===, step = %d", step);

	if (step == FA_STEP_NTC_3_START - 1 || step == FA_STEP_NTC_3_START) {
		// 一直在这里循环, 直到下一个step测试OK
		set_fa_test_step(FA_STEP_NTC_3_START);

		// 防止上一个step显示字符串太快而看不到, 取代DELAYs(2), 因为测量灯电流需要采样很多次 .
		for (int i = 0; i < 200; i++, WDT_CLEAN(), delay1ms(10)) {
			adc_ontimer();
		}

		//uint32_t lamp_mv = ((get_ntc3_mv() * 1000) / (1000 + 30000));
		uint32_t lamp_ma = (((get_ntc3_mv() * 1000) / (1000 + 30000)) * 1000) / 20;
		if (lamp_ma >= 180 && lamp_ma <= 4500) {
			lamp_ma += 10000;
		}

		if (lamp_ma < 10600 || lamp_ma > 11000) {
#if HAS_LAMP
			display_str(SENSOR_E3_STR);
#endif
			FTM_LOG("lamp_ma(%d), show \"%s\"", lamp_ma, SENSOR_E3_STR);
		} else {
			set_fa_test_step(FA_STEP_NTC_3_END);
#if HAS_LAMP
			fa_show_step_str();
#endif
			FTM_LOG("lamp_ma(%d), enter Next Step", lamp_ma);
		}
	}
}
#endif

#if HAS_IRDA
static void ftm_fa_irda_test() {
	uint8_t step = get_fa_test_step();

	if (step == FA_STEP_IRDA_START - 1 || step == FA_STEP_IRDA_START) {
		// 一直在这里循环, 直到下一个step测试OK
		set_fa_test_step(FA_STEP_IRDA_START);

		uint8_t times = 0;
		bool old_state = false, new_state = false;

		for (int i = 0; i < 200; i++, WDT_CLEAN(), delay1ms(50)) {
			if ((new_state = !!gpio_read(IRDA_GPIO)) != old_state) {
				FTM_LOG("state change: %d ==> %d", old_state, new_state);
				old_state = new_state;
				if (++times >= 2) {
					set_fa_test_step(FA_STEP_IRDA_END);
#if HAS_LAMP
					fa_show_step_str();
#endif
					break;
				}
			}
		}
	}
}
#endif

#if HAS_COM
static void ftm_fa_com_test() {
	uint8_t step = get_fa_test_step();

	if (step == FA_STEP_COM_START - 1) {
		// 这里不循环, 等待当前step测试OK
		set_fa_test_step(FA_STEP_COM_START);

		FTM_LOG("COM Test Start ...");
		SEND_RESULT(FTM_COM_TEST, true);
	}
}
#endif

#if (HAS_485_UART || (ZQ_COMM_PLATFORM == 582 && HAS_COM))
/*
* Description: 	FA测试结果上报给DTU
* Parameter:
* Return：
* Others:		借助工装(485/com)
*/
static void ftm_fa_report_to_dtu() {
	uint8_t buf[1 + 32 + 1] = { 0 };
	buf[0] = FA_TEST_OK;
	memcpy((char*)buf + 1, (char*)g_devinfo.sn, g_devinfo.sn_len);
	buf[1 + g_devinfo.sn_len] = 0;

	ftm_pkt_send(1, FTM_M0_REPORT_FA_RESULT, buf, 1 + g_devinfo.sn_len + 1);
	FTM_LOG("!!! Note: when 485 uart or COMx report FA, Must not plugin M0 com !!!");
}
#endif

#if (ZQ_COMM_PLATFORM == 8850 || ZQ_COMM_PLATFORM == 8910)
/*
* Description: 	FA测试结果上报给Cat1模块(自带)
* Parameter:
* Return：
* Others:
*/
static void ftm_fa_report_to_cat1() {
	uint8_t buf[] = { FA_TEST_OK };
	ftm_pkt_send(0, FTM_M0_REPORT_FA_RESULT, buf, sizeof(buf));
	FTM_LOG("FA_TEST_OK, report to Cat1, THEN STOP");
}
#endif

#if (ZQ_COMM_PLATFORM == 582 && !HAS_COM)
/*
* Description: 	FA测试结果不上报
* Parameter:
* Return：
* Others:		没有对外串口或485, 也没有自带Cat1
*/
static void ftm_fa_report_nothing() {
	FTM_LOG("Only M0, and no COM or 485, Can not report FA, change to work status directly");
	ftm_bootmode_write(MODE_WK, true);
}
#endif

static void ftm_fa_test_complete(void) {
	uint8_t step = get_fa_test_step();
	if (step == FA_STEP_STOP - 1) {
		FTM_LOG("=== COMPLETE ===, step = %d", step);
		set_fa_test_step(FA_STEP_STOP);

#ifdef FA_LED_BANK_TEST	
#if HAS_LAMP
		display_str(LED_FA_OK_STR);
#endif
		DELAYms(3 * 1000);
		// 3s后重头再来
		set_fa_test_step(FA_STEP_LAMP);
#else
		DELAYms(1 * 1000);

#if ZQ_COMM_PLATFORM == 485
		// 这个宏暂时可以表示仅有M0模块
		// 0, 有485, 则可以借助485工装上传FA数据
		ftm_fa_report_to_dtu();
#elif (ZQ_COMM_PLATFORM == 582)	
		// 这个宏暂时表示仅有M0模块
#if HAS_COM
		// 1, 有对外串口, 则可以借助DTU上传FA数据
#if 1
		ftm_fa_report_to_dtu();
#else
		// 这种是旧项目的上传给DTU的数据格式 (这段代码先不删除, 可以用来参考别的纯温控器是怎么上报FA数据给DTU的)
		uint8_t buf[50], off = 0;
		buf[off++] = 0xAA;
		buf[off++] = g_devinfo.sn_len;
		memcpy(&buf[off], g_devinfo.sn, g_devinfo.sn_len);
		off += g_devinfo.sn_len;
		buf[off++] = 0xBB;
		SEND_TO_MASTER(buf, off);
#endif
#else
		// 2, 没有对外串口, 直接切Work状态并重启
		ftm_fa_report_nothing();
#endif
#else
		// 这里直接发送命令给Cat1, 告知FA测试OK了 !
		ftm_fa_report_to_cat1();
#endif
#endif
	}
}

/*
* Description: 	FA测试完成并上传服务器成功
* Parameter:
* Return：
* Others:		WIFI&4G灯亮, 蜂鸣器叫一声, 并且显示 888
* NOTE:			2s后系统重启
*/
static void ftm_fa_upload_success(void) {
	DISP_ICON(disp_item_wifi, true); 	// WIFI
	DISP_ICON(disp_item_4g, true); 		// LED_4G

#if HAS_LAMP
	fa_show_step_str();
#endif

#if HAS_BEEP
	ftm_sa_beep_test();
#endif

#ifdef FA_TEST_OK_REBOOT
	FTM_LOG("2, M0 set work too, REBOOT after 2s");
	delay1ms(2 * 1000);				// 取代DELAYs(2), 期间不能再重复处理cat1过来的命令了！
	SYS_ResetExecute();
#else
	FTM_LOG("2, M0 set work too, DON'T REBOOT");
#endif
}

/*
* Description: 	FA失败的显示
* Parameter:
* Return：
* Others:		led灯全灭, 蜂鸣器叫一声, 并且显示 xx
* NOTE:
*/
static void ftm_fa_fail_display(char* str) {
#if HAS_LAMP
	display_str(str);
	all_leds_enable(false);
#endif

#if HAS_BEEP
	ftm_sa_beep_test();
#endif

	FTM_LOG("2, fail, show %s", str);
}



/****************************pkt functions***********************************/
/*
* Description: 	uart1(PC)的接收回调
* Parameter:
* @data:		数据内容
* Return：
* Others:		Note: 这个函数里禁止用printf打印, 极大可能会导致uart接收数据有问题 !!!
*/
static void master_rx_cb(uint8_t* data, uint16_t* plen) {
	if (receive_length + (*plen) < RECV_BUF_SIZE) {
		memcpy(receive_buffer + receive_length, data, *plen);
		receive_length += *plen;
	}

	// 清除
	*plen = 0;
}

static void master_rx_flush(void) {
	receive_length = 0;
}

/*
* Description: 	master接收包的处理
* Parameter:
* @pkt:			解析包(cmd)
* Return：
* Others:
*/
static void master_packet_process(pkt_t* pkt) {
	uint8_t buf[128] = { 0 };
	uint8_t data_len = 0;

	uint8_t cmd_type = (pkt->data_len == 0) ? CMD_GET : CMD_SET;

	FTM_LOG("cmd_type = %s, pkt->cmd = 0x%02x", (cmd_type == CMD_GET ? "READ" : "WRITE"), pkt->cmd);

	switch (pkt->cmd) {

	case FTM_SN:
		if (cmd_type == CMD_GET) {
			data_len = ftm_sa_sn_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_sa_sn_write(pkt->data, pkt->data_len));
		}
		break;

	case FTM_HW:
		data_len = ftm_sa_hw_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_SW:
		data_len = ftm_sa_sw_read(buf);
		SEND_RESPONSE(pkt->cmd, buf, data_len);
		break;

	case FTM_BOOTMODE:
		if (cmd_type == CMD_GET) {
			data_len = ftm_bootmode_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			// 每次用串口工具设置SA或FA都要拔电重启太麻烦了, 我修改一下命令, 让板子自动重启
			SEND_RESULT(pkt->cmd, ftm_bootmode_write(pkt->data[0], pkt->data[1] == 1));

#if HAS_485_UART || HAS_COM
			// FA测试结束上传结果成功时, 别的485DTU返回过来的数据, 做特殊处理
			if (pkt->data_len == 3 && pkt->data[0] == MODE_WK && pkt->data[1] == 0x99 && pkt->data[2] == 0x99) {
				ftm_fa_upload_success();
			} else if (pkt->data_len == 1 && pkt->data[0] == 0) {
				// 这里是DTU回复过来的
				ftm_fa_fail_display(UPLOAD_FAIL_STR);
			}
#endif
		}
		break;

	case FTM_DEVICE_ID:
#if HAS_DEVICE_ID
		if (cmd_type == CMD_GET) {
			data_len = ftm_sa_did_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_sa_did_write(pkt->data, pkt->data_len));
		}
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_BT_UUID:
#if HAS_UUID
		if (cmd_type == CMD_GET) {
			data_len = ftm_sa_ble_read(buf);
			SEND_RESPONSE(pkt->cmd, buf, data_len);
		} else {
			SEND_RESULT(pkt->cmd, ftm_sa_ble_write(pkt->data, pkt->data_len));
		}
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_RELAY_1:
		SEND_RESULT(pkt->cmd, ftm_sa_relay_test(0, false));
		break;
	case FTM_RELAY_2:
		SEND_RESULT(pkt->cmd, ftm_sa_relay_test(1, false));
		break;
	case FTM_RELAY_3:
		SEND_RESULT(pkt->cmd, ftm_sa_relay_test(2, false));
		break;
	case FTM_RELAY_4:
		SEND_RESULT(pkt->cmd, ftm_sa_relay_test(3, false));
		break;

	case FTM_POWER_METER:
#if HAS_POWER_METER
		SEND_RESULT(pkt->cmd, ftm_sa_power_meter_cal(cmd_type == CMD_GET, pkt->data));
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_IRDA:
#if HAS_IRDA
		SEND_RESULT(pkt->cmd, ftm_sa_irda_test());
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_ZHAOMING:
#if HAS_IO_LED
		SEND_RESULT(pkt->cmd, ftm_sa_led_test(0, false));
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_LENGNING:
#if HAS_IO_LED
		SEND_RESULT(pkt->cmd, ftm_sa_led_test(1, false));
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_ZHENGFA:
#if HAS_IO_LED
		SEND_RESULT(pkt->cmd, ftm_sa_led_test(2, false));
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_BEEP:
#if HAS_BEEP
		SEND_RESULT(pkt->cmd, ftm_sa_beep_test());
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_NTC_1:
#if HAS_NTC_1
		SEND_RESULT(pkt->cmd, ftm_sa_ntc1_test());
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_NTC_2:
#if HAS_NTC_2
		SEND_RESULT(pkt->cmd, ftm_sa_ntc2_test());
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_485_TEST:
#if HAS_485_UART
		SEND_RESULT(pkt->cmd, true);
#else
		SEND_RESULT(pkt->cmd, -1);
#endif
		break;

	case FTM_COM_TEST:
#if HAS_COM
		// 这里是M0的(对外)串口收到(工装)的回复
		if (pkt->data_len >= 1 && pkt->data[0]) {
			FTM_LOG("COM Test End ... Success");
			set_fa_test_step(FA_STEP_COM_END);
		} else {
			FTM_LOG("COM Test End ... Fail");
		}
#endif
		break;
	}
}

/*
* Description: 	pc接收包的handler
* Parameter:
* @rx_buffer:	接收数据buffer
* @rx_length:	接收数据长度
* Return：		0: 未接收完成; -1: 接收错误, 清除; 1, 接收完成, 处理后清除
* Others:
*/
int8_t ftm_master_rx_handler(uint8_t* rx_buffer, uint16_t rx_length) {
	if (rx_length == 0) {
		return 0;
	}

	int8_t pkt_result = 0;
	pkt_t pkt = { 0 };

	FTM_HEX_BUF_LOG("rx_buffer", rx_buffer, rx_length);

	pkt_result = get_pc_pkt(rx_buffer, rx_length, &pkt);
	if (pkt_result > 0) {
		if ((pkt.cmd & FTM_SLAVE_CMD_PREFIX) == FTM_SLAVE_CMD_PREFIX) {
			FTM_LOG("command(0x9xxx) from PC, send to the Cat1");
			SEND_TO_SLAVE(rx_buffer, rx_length);
		} else {
			FTM_LOG("command(0x8xxx) from PC, M0 process it");
			master_packet_process(&pkt);
		}

		master_rx_flush();
	}

	return pkt_result;
}

/*
* Description: 	uart0(Cat1)的接收回调
* Parameter:
* @data:		数据内容
* Return：
* Others:		Note: 这个函数里禁止用printf打印, 极大可能会导致uart接收数据有问题 !!!
*/
static void slave_rx_cb(uint8_t* data, uint16_t* plen) {
	// FTM_LOG("rece data, len = %d", *plen);

	if (receive_length_slave + (*plen) < RECV_BUF_SIZE) {
		memcpy(receive_buffer_slave + receive_length_slave, data, *plen);
		receive_length_slave += *plen;
	}

	// 清除
	*plen = 0;
}

/*
* Description: 	清除uart0(Cat1)的接收buffer
* Parameter:
* @pkt_len:		长度
* Return：
* Others:		有可能cat1同时发了两个包过来, 所以不能一次全部清除掉, 而是仅清除已解析过的有效长度, 剩余的buffer还可以继续解析 ！！
*/
static void slave_rx_flush(uint16_t pkt_len) {
	receive_length_slave -= pkt_len;
	if (receive_length_slave > 0) {
		FTM_LOG("========== CLEAR: %d, LEFT: %d", pkt_len, receive_length_slave);
		memcpy(receive_buffer_slave, receive_buffer_slave + pkt_len, receive_length_slave);
	}
}

#if HAS_UUID
static uint16_t get_bt_mac(uint8_t* rx_buffer) {
	uint8_t btMac[6] = { 0 };
	GetMACAddress(&btMac);

	FTM_LOG("HAS_UUID && cmd = 0x%X, get bt mac by M0", FTM_WIFI_BT_MAC);

	// AA 00 FF FF FF FF FF FF 91 0B 0C D6 8F 9B 6C AF 87 E8 8D A6 89 EC 93 11 BB 
	// WIFI = { D6 8F 9B 6C AF 87 }, BT = { E8 8D A6 89 EC 93 } 
	uint16_t off = PKT_HEAD_SZ + 6;

	for (int i = 0; i < 6; i++) {
		rx_buffer[off++] = btMac[(6 - 1) - i];
	}

	// 最后还要重新算一下buff的checkSum
	uint8_t sum = ftm_pkt_sum(rx_buffer, off);
	rx_buffer[off++] = sum;
	rx_buffer[off++] = PKT_SUFFIX;

	FTM_HEX_BUF_LOG("get_bt_mac", rx_buffer, off);

	return off;
}
#endif

static void print_cat1_cmd_result(uint16_t cmd, uint8_t* data, uint32_t data_len) {
	char szData[150] = { 0 };
	memcpy(szData, (char*)data, data_len);
	szData[data_len] = 0;

	char cmdStr[20] = { 0 };

	if (cmd == 0x9100) {
		strcpy(cmdStr, "SN");
	} else if (cmd == 0x9101) {
		strcpy(cmdStr, "HW");
	} else if (cmd == 0x9102) {
		strcpy(cmdStr, "SW");
	} else if (cmd == 0x9105) {
		strcpy(cmdStr, "UUID");
	} else if (cmd == 0x9106) {
		strcpy(cmdStr, "BOOTMODE");
	} else if (cmd == 0x9107) {
		strcpy(cmdStr, "ICCID");
	} else if (cmd == 0x9108) {
		strcpy(cmdStr, "SIM NUM");
	} else if (cmd == 0x9109) {
		strcpy(cmdStr, "IMSI");
	} else if (cmd == 0x910A) {
		strcpy(cmdStr, "IMEI");
	} else if (cmd == 0x910B) {
		strcpy(cmdStr, "WIFI BT MAC");
	} else if (cmd == 0x910D) {
		strcpy(cmdStr, "PORT URL");
	} else if (cmd == 0x9131) {
		strcpy(cmdStr, "GPRS");
	} else if (cmd == 0x9132) {
		strcpy(cmdStr, "WIFI");
	} else if (cmd == 0x9134) {
		strcpy(cmdStr, "SPI");
	} else if (cmd == 0x9135) {
		strcpy(cmdStr, "MP3");
	} else if (cmd == 0x9302) {
		strcpy(cmdStr, "T40");
	} else if (cmd == 0x9300) {
		strcpy(cmdStr, "FA UPLOAD");
	}

	if (data_len == 1) {
		if (cmd == 0x9106) {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, (data[0] == MODE_SA ? "SA | OK" : (data[0] == MODE_FA ? "FA | OK" : "WORK")));
		} else if (cmd == 0x9131) {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s, %d)", cmd, cmdStr, ((int8_t)data[0] >= -90 ? "Ok" : "Fail"), (int8_t)data[0]);
		} else if (cmd == 0x9300) {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, (data[0] == MODE_WK ? "Ok" : "Fail"));
		} else {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, (data[0] == OK ? "Ok" : (data[0] == FAIL ? "Fail" : (data[0] == NOT_SUPPORT ? "Not Support" : "Not Set"))));
		}
	} else {
		if (cmd == 0x9105) {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, [%02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X]-0x%02X(%d)-0x%02X(%d))", cmd, cmdStr, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13], data[14], data[15], ((data[16] << 8) + data[17]), ((data[16] << 8) + data[17]), ((data[18] << 8) + data[19]), ((data[18] << 8) + data[19]));
		} else if (cmd == 0x910B) {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, [%02X:%02X:%02X:%02X:%02X:%02X]-[%02X:%02X:%02X:%02X:%02X:%02X])", cmd, cmdStr, data[0], data[1], data[2], data[3], data[4], data[5], data[6], data[7], data[8], data[9], data[10], data[11]);
		} else if (cmd == 0x910D) {
			szData[data_len - 2] = 0;
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %d:%s)", cmd, cmdStr, (data[data_len - 2] << 8) + (data[data_len - 1] & 0xff), szData);
		} else if (cmd == 0x9302) {
			while (--data_len >= 0) {
				if (data[data_len] == FAIL) {
					FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, "Fail");
					return;
				}
			}
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, "Ok");
		} else {
			FTM_LOG("SEND_RESPONSE: (0x%02X-%s, %s)", cmd, cmdStr, szData);
		}
	}
}

/*
* Description: 	cat1接收包的处理
* Parameter:
* @pkt:			解析包(cmd)
* Return：
* Return：		0: 未接收完成; -1: 接收错误, 清除; 1, 接收完成, 处理后清除
*/
static void slave_packet_process(pkt_t* pkt) {
	uint8_t buf[128] = { 0 };
	uint8_t data_len = 0;

	switch (pkt->cmd) {

	case CAT1_QUERY_M0_INFO:
		data_len = ftm_sa_cmd01(buf);
		businiss_pkt_send(CAT1_QUERY_M0_INFO, buf, data_len);
		break;

	case FTM_SET_SLAVE_TO_WK:
		/* 因为这个命令是共用的, 所以要加判断是否为按键设置的 */
		if (is_ftm_mode()) {
			// M0在SA或FA模式下, 可以通过按键5次(而不是PC发命令的方式)设置Cat1到Work模式,
			// 然后收到回应后, 再把M0也设置成Work模式, 不重启(后续还继续按键可以切换到相反的模式) .
			if (g_press_key_times >= 5) {
				FTM_LOG("1, %s --> WORK (Don't reboot), press key1 (%d)", (is_ftm_fa_mode() ? "FA" : "SA"), g_press_key_times);

				// 这里做个特殊处理, SA模式切到Work时, 把key_times置成-15 (+5后便到-10, 那下面的判断g_press_key_times == -10就有效了)
				if (!is_ftm_fa_mode()) {
					g_press_key_times = -15;
				}

				ftm_bootmode_write(MODE_WK, false);
			} else {
				FTM_LOG("1, %s, PC command, M0 need not process it", (is_ftm_fa_mode() ? "FA" : "SA"));
			}
		} else {
			// M0到了Work模式, 是因为SA或FA的5次按键设置
			if (g_press_key_times == -10) {
				FTM_LOG("3, SA --> Work --> FA (Reboot), press key1 (%d)", g_press_key_times);
				ftm_bootmode_write(MODE_FA, true);
			} else if (g_press_key_times == 10) {
				FTM_LOG("3, FA --> Work --> SA (Reboot), press key1 (%d)", g_press_key_times);
				ftm_bootmode_write(MODE_SA, true);
			} else {
				FTM_LOG("3, SA | FA --> Work, PC command, M0 need not process it");
			}
		}
		break;

	case FTM_M0_REPORT_FA_RESULT:
		// Cat1在收到M0测试完成后, 先上传服务器(IMEI);
		// 并设置Work模式, 然后发命令给M0, 让M0也设置成Work模式,
		if (pkt->data[0] == MODE_WK) {
			FTM_LOG("1, Cat1 upload complete, set to work self");
			ftm_bootmode_write(MODE_WK, false);
#if 0
			// M0也不必再回应, 直接重启;
			uint8_t buf[] = { OK };
			ftm_pkt_send(0, pkt->cmd, buf, sizeof(buf));
#endif
			ftm_fa_upload_success();
		} else {
			FTM_LOG("1, Cat1 upload fail, do nothing");
			ftm_fa_fail_display(UPLOAD_FAIL_STR);
		}
		break;

	case FTM_PLAY_MP3:
#if HAS_MP3
		// 这里是收到MP3播放的结果
		if (pkt->data_len >= 1 && pkt->data[0]) {
			FTM_LOG("MP3 Test End ... Success");
		} else {
			FTM_LOG("MP3 Test End ... Fail");
		}

		//delay1ms(1000);	[这里为何不能用delay?]
		set_fa_test_step(get_fa_test_step() + 1);
#endif
		break;

	default:
		break;
	}
}

/*
* Description: 	cat1接收包的handler
* Parameter:
* @uartx:		uart0数据(struct)
* Return：
* Others:
*/
int8_t ftm_slave_rx_handler(uint8_t* rx_buffer, uint16_t rx_length) {
	if (rx_length == 0) {
		return 0;
	}

	int8_t pkt_result = 0;
	pkt_t pkt = { 0 };

	FTM_HEX_BUF_LOG("rx_buffer", rx_buffer, rx_length);

	uint8_t pkt_prefix[] = PKT_PREFIX;

	// 2024-06-22: 模式同步的命令数据 + mp3播放的结果数据在一起
	// rx_buffer[18] = { AA 04 01 AF AA 00 FF FF FF FF FF FF 91 35 01 01 6C BB }
	if (!memcmp(rx_buffer, pkt_prefix, sizeof(pkt_prefix))) {
		FTM_LOG("(1), FTM's CMD: Cat1 to M0");
		pkt_result = get_pc_pkt(rx_buffer, rx_length, &pkt);
	} else {
		// 这里单独处理Cat1发过来的01命令
		FTM_LOG("(2), WORK's CMD: Cat1 to M0");
		pkt_result = get_business_pkt(rx_buffer, rx_length, &pkt);
	}

	if (pkt_result > 0) {
		if ((pkt.cmd & FTM_SLAVE_CMD_PREFIX) == FTM_SLAVE_CMD_PREFIX) {
			//CAT1 -> M0 -> PC
			FTM_LOG("Receive Cat1 response(0x9xxx), send to PC");

			// cat1回应过来的结果, 直接在M0的uart打印查看
			print_cat1_cmd_result(pkt.cmd, pkt.data, pkt.data_len);

#if HAS_UUID
			// 这里M0芯片上有真实的蓝牙地址, 截取处理一下, 再回复给PC
			if (pkt.cmd == FTM_WIFI_BT_MAC) {
				rx_length = get_bt_mac(rx_buffer);
			}
#endif

			// 转发给PC
			SEND_TO_MASTER(rx_buffer, rx_length);
		}

		slave_packet_process(&pkt);
		slave_rx_flush(pkt.len);
	}

	return pkt_result;
}

/****************************FTM入口函数*************************************/
static void DebugInit(void) {
	gpio_config(DEBUG_TX_GPIO, GPIO_ModeOut_PP_20mA);
	gpio_write(DEBUG_TX_GPIO, true);

	UART2_DefInit();
	GPIOPinRemap(ENABLE, RB_PIN_UART2);
}

#if HAS_BATTERY
static void lpMode() {
	if (!gpio_read(BATTERY_GPIO)) {
		// 如果没有12V市电, 则进入低功耗模式
		FTM_LOG("Battery, change to Low Power mode");

		// 这里直接调用进入低功耗模式的函数 ?
		extern int CtrlRunLP(void);
		CtrlRunLP();
	}
}
#endif

#if HAS_UUID
static void bleBroadcast() {
	static bool initBle = false;
	if (g_devinfo.ble_major != 0 && g_devinfo.ble_major != BLE_MAJOR_DEFAULT) {
		if (!initBle) {
			FTM_LOG("BLE broadcast, call ble init");
			// 这里直接调用ble初始化的函数 ?
			void ZyLoadFtmData(void);
			extern void BleExchInit(uint8_t force);

			ZyLoadFtmData();
			BleExchInit(0);
			initBle = true;
		}
		TMOS_SystemProcess();
	}
}
#endif

/*
* Description:  测试任务(基本都是FA的)
* Parameter:
* Return：
* Others:
*/
static void test_task(bool is_fa_mode) {
#if HAS_LAMP
	if (is_lamp_tick()) {
		reset_lamp_tick();

		// 2, 数码灯管测试(循环1s显示)
		ftm_lamp_test();
	}
#endif

	if (is_fa_mode) {
		// 1, 继电器测试
		ftm_fa_relay_test();

		// 2, mp3测试
#if HAS_MP3
		ftm_fa_mp3_test();
#endif

		// 3, 按键测试
		ftm_fa_key_test();

#if HAS_IO_LED
		// 4. LED 测试
		ftm_fa_led_test();
#endif

#if HAS_DOOR_1
		// 5.1, 门(1)开关测试
		ftm_fa_door1_test();
#endif

#if HAS_DOOR_2
		// 5.2, 门(2)开关测试
		ftm_fa_door2_test();
#endif

#if HAS_SYS
		// 6, 系统开关测试
		ftm_fa_sys_test();
#endif

#if HAS_NTC_1
		// 7.1, 温度传感器1测试
		ftm_fa_ntc1_test();
#endif

#if HAS_NTC_2
		// 7.2, 温度传感器2测试
		ftm_fa_ntc2_test();
#endif

#if HAS_NTC_3
		// 7.3, 温度传感器3测试
		ftm_fa_ntc3_test();
#endif

#if HAS_IRDA
		// 8, 红外传感器测试
		ftm_fa_irda_test();
#endif

#if HAS_COM
		// 9, 预留的对外串口测试
		ftm_fa_com_test();
#endif

		// 检查是否要到了最后一步: 这里通知Cat1上传结果到服务器
		ftm_fa_test_complete();

	} else {
		// sa模式下按键5次的处理 (把M0和cat1切到work模式)
		ftm_sa_key_process();
	}
}

void process_uart_pc(void) {
	if (ftm_master_rx_handler(receive_buffer, receive_length) < 0) {
		master_rx_flush();
	}
}

void process_uart_cat1(void) {
	if (ftm_slave_rx_handler(receive_buffer_slave, receive_length_slave) < 0) {
		slave_rx_flush(receive_length_slave);
	}
}

/*
* Description: 	ftm测试的入口函数
* Parameter:
* Return：
* Others:		main() 调用此函数 .

*/
void ftm_start(void) {
	// 1, 配置log输出口 (如果在此之前打印FTM log的话(包括main.c中的PRINT也是)就会宕机, 所以先配置TX2)
	DebugInit();
	devinfo_load();

#ifdef FA_LED_BANK_TEST
	// 如果是灯板测试, 强行进入FA测试
	ftm_bootmode_write(MODE_FA, false);
#endif

	bool ftm_mode = is_ftm_mode();

	/* 读M0版本号, 不然每次都用串口命令去查询 */
	ftm_sa_sw_read(NULL);

	if (!ftm_mode) {
		return;
	}

	// 2, 关闭watchDog
	WWDG_ResetCfg(0);

	// 3, 配置uart1 (PC), uart0 (Cat1)
#if (ZQ_COMM_PLATFORM == 8910)
	UartDbgInit(115200, master_rx_cb);
	UartIotInit(230400, slave_rx_cb);
#elif (ZQ_COMM_PLATFORM == 8850) || (ZQ_COMM_PLATFORM == 485) || (ZQ_COMM_PLATFORM == 582)
	UartInit(1, 115200, receive_buffer, RECV_BUF_SIZE, master_rx_cb);
	UartInit(0, 230400, receive_buffer_slave, RECV_BUF_SIZE, slave_rx_cb);
#else
#error "please add ZQ_COMM_PLATFORM == xx"
#endif

	bool is_fa_mode = is_ftm_fa_mode();

	/*******************************正式执行ftm代码***************************************/
	if (is_fa_mode) {
#ifdef FA_LED_BANK_TEST
		// 如果是灯板测试, 直接进入led循环显示
		set_fa_test_step(FA_STEP_LAMP);
#else
#if HAS_LAMP
		display_str(BOOT_MODE_FA);
#endif
		// init step to none
		set_fa_test_step(FA_STEP_NONE);
#endif
		DELAYms(1 * 1000);

#if HAS_BATTERY
		lpMode();
#endif
	} else {
#if HAS_LAMP
		display_str(BOOT_MODE_SA);
		set_fa_test_step(FA_STEP_LAMP);
#else
		set_fa_test_step(FA_STEP_NONE);
#endif
		DELAYms(2 * 1000);
	}

	while (1) {
		if (is_loop_tick()) {
			// 复位 10ms 定时标志
			reset_loop_tick();

#if HAS_POWER_METER
			static uint8_t x = 0;
			if (++x % 100 == 0) {
				short vol, cur, pow;
				PowMeterGather(&vol, &cur, &pow);
				if (cur != 0) {
					FTM_LOG("8850: vol=%d, cur=%d, pow=%d", vol, cur, pow);
				}
			}
#endif

#if HAS_UUID
			bleBroadcast();
#endif
			// 4, 处理pc工具发过来的命令
			process_uart_pc();

			// 5, FTM里处理CAT1过来的命令
			process_uart_cat1();

			// 6, 其他测试都放到一个函数中(基本都是FA的)
			test_task(is_fa_mode);
		}

		WDT_CLEAN();
	}
}
